package de.rj.urwalking.util;

import android.os.AsyncTask;

import com.threed.jpct.Camera;
import com.threed.jpct.Object3D;
import com.threed.jpct.SimpleVector;
import com.threed.jpct.World;

import de.rj.urwalking.config.Constants;
import de.rj.urwalking.data.Perspective;

/**
 * 
 * Contains some convenience methods for manipulating jpct's Camera.
 * 
 * @author rjj
 * 
 */

public class CameraUtil {

	// using static fields for intermediate variables to avoid allocations every frame
	private static SimpleVector rotation = new SimpleVector();
	private static SimpleVector direction = new SimpleVector();
	private static SimpleVector upVector = new SimpleVector();

	/**
	 * rotate the camera a given angle around the global z-axis
	 * 
	 * @param angle
	 * @param camera
	 */

	public static void rotateHorizontal(float angle, Camera camera) {
		upVector = camera.getUpVector();
		direction = camera.getDirection();
		rotation = SimpleVector.create(0, 0, angle);

		upVector = upVector.rotate(rotation);
		direction = direction.rotate(rotation);
		camera.setOrientation(direction, upVector);

	}

	/**
	 * rotate the camera to a given angle around the global z-axis
	 * 
	 * @param targetAngle
	 * @param camera
	 * @param perspective
	 */

	public static void rotateHorizontalTo(float targetAngle, Camera camera, int perspective) {

		double currentAngle = 0;

		if (perspective == Perspective.TYPE_3D_ABOVE) {
			currentAngle = Math.atan2(camera.getUpVector().y, camera.getUpVector().x);
		} else if (perspective == Perspective.TYPE_3D_STREET) {
			currentAngle = Math.atan2(camera.getDirection().y, camera.getDirection().x);

		}

		double angleDiff = -targetAngle - currentAngle;

		rotateHorizontal((float) angleDiff, camera);
	}

	/**
	 * Moves the camera forward, if possible, i.e. if there's no collision.
	 * 
	 * @param world
	 */

	public static void moveForward(World world) {
		direction = SimpleVector.create(world.getCamera().getDirection().x, world.getCamera().getDirection().y, 0);
		world.checkCameraCollision(direction, direction.length() / 4, 1.5f, Camera.SLIDE);
	}


	/**
	 * Moves the camera backward, if possible, i.e. if there's no collision.
	 * 
	 * @param world
	 */

	public static void moveBackward(World world) {
		direction = SimpleVector.create(-world.getCamera().getDirection().x, -world.getCamera().getDirection().y, 0);
		world.checkCameraCollision(direction, direction.length() / 4, 1.5f, Camera.SLIDE);

	}
	
	/**
	 * Moves the camera forward, if possible, i.e. if there's no collision.
	 * 
	 * @param world
	 */

	public static void stepForward(World world, float stepLength) {
		direction = SimpleVector.create(world.getCamera().getDirection().x, world.getCamera().getDirection().y, 0);
		world.checkCameraCollision(direction, stepLength, 1.5f, Camera.SLIDE);
	}

	/**
	 * Animates the camera when switching from street view to overview.
	 * 
	 */
	
	public static class FlyUpTask extends AsyncTask<Double, Integer, Boolean> {

		private Camera camera;

		public FlyUpTask(Camera camera) {
			this.camera = camera;
		}

		@Override
		protected Boolean doInBackground(Double... params) {

			final int steps = 100;
			SimpleVector startPosition = camera.getPosition();
			final float heightDiff = (float) (params[0] - startPosition.z);

			final float startAngle = (float) Math.asin(camera.getDirection().z);
			final float angleDiff = (float) (Math.PI / 2 + startAngle);

			for (int i = 1; i <= steps; i++) {
				camera.setPosition(startPosition.x, startPosition.y, startPosition.z + ((float) i / steps) * heightDiff);

				float angle = -((float) 1 / (float) steps) * angleDiff;

				camera.rotateX(angle);

				try {
					Thread.sleep(16);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			return null;
		}

	}

	/**
	 * Animates the camera when switching from overview to street view.
	 *
	 */
	public static class FlyDownTask extends AsyncTask<Double, Integer, Boolean> {

		private Camera camera;

		public FlyDownTask(Camera camera) {
			this.camera = camera;
		}

		@Override
		protected Boolean doInBackground(Double... params) {

			final int steps = 100;
			final float targetHeight = 1.8f;
			SimpleVector startPosition = camera.getPosition();
			float heightDiff = startPosition.z - targetHeight;

			for (int i = 1; i <= steps; i++) {
				camera.setPosition(startPosition.x, startPosition.y, startPosition.z - ((float) i / steps) * heightDiff);

				float angle = (float) (((float) 1 / (float) steps) * Math.PI / 2f);

				camera.rotateX(angle);

				try {
					Thread.sleep(16);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

			return null;
		}

	}
	
	
	/**
	 * Animates the camera when switching from overview to slightly higher street view.
	 *
	 */
	public static class LegoFlyDownTask extends AsyncTask<Double, Integer, Boolean> {

		private Camera camera;

		public LegoFlyDownTask(Camera camera) {
			this.camera = camera;
		}

		@Override
		protected Boolean doInBackground(Double... params) {

			final int steps = 100;
			final float targetHeight = 3.0f;
			SimpleVector startPosition = camera.getPosition();
			float heightDiff = startPosition.z - targetHeight;

			for (int i = 1; i <= steps; i++) {
				camera.setPosition(startPosition.x, startPosition.y, startPosition.z - ((float) i / steps) * heightDiff);

				float angle = (float) (((float) 1 / (float) steps) * Math.PI / 2f);

				camera.rotateX(angle);

				try {
					Thread.sleep(16);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

			return null;
		}
	}

	/**
	 * Moves the camera smoothly from one location to another.
	 *
	 */
	
	public static class MoveTask extends AsyncTask<Float, Integer, Boolean> {

		private Camera camera;
		private Object3D locationIndicator;

		public MoveTask(Camera camera, Object3D locationIndicator) {
			this.camera = camera;
			this.locationIndicator = locationIndicator;
		}

		@Override
		protected Boolean doInBackground(Float... params) {

			final int steps = 10;

			final float x = params[0];
			final float y = params[1];

			SimpleVector startPosition = camera.getPosition();
			SimpleVector endPosition = new SimpleVector(x, y, startPosition.z);
			SimpleVector difference = endPosition.calcSub(startPosition);

			float length = difference.length();

			for (int i = 1; i <= steps; i++) {

				camera.moveCamera(difference.normalize(), length / steps);
				locationIndicator.setOrigin(new SimpleVector(camera.getPosition().x, camera.getPosition().y,
						locationIndicator.getOrigin().z));

				try {
					Thread.sleep(16);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

			return null;
		}

	}

	/**
	 * Zooms (actually: moves) the camera closer to the floor.  
	 *
	 */
	
	public static class ZoomTask extends AsyncTask<Void, Void, Void> {

		private Camera camera;

		public ZoomTask(Camera camera) {
			this.camera = camera;
		}

		@Override
		protected Void doInBackground(Void... params) {
			final int steps = 10;

			for (int i = 1; i <= steps; i++) {

				camera.moveCamera(Camera.CAMERA_MOVEIN, 5f);

				if (camera.getPosition().z < 10) {
					camera.setPosition(camera.getPosition().x, camera.getPosition().y, 10);
					return null;
				}
				try {
					Thread.sleep(16);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

			return null;
		}

	}

}
